/*
 * MIT License
 *
 * Copyright (c) 2023-present, tangli
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tony.test.crypto.symmetric

import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import tony.crypto.symmetric.SymmetricCrypto
import javax.crypto.Cipher
import java.security.SecureRandom

/**
 * SymmetricCrypto 接口测试类
 *
 * 测试对称加密接口的核心功能，包括加密解密等
 *
 * @author tangli
 * @date 2025/01/22 20:00
 */
@DisplayName("SymmetricCrypto 接口测试")
class SymmetricCryptoTest {

    /**
     * 测试用的对称加密实现
     */
    private val crypto: SymmetricCrypto = tony.crypto.symmetric.Aes

    /**
     * 生成测试用的 AES 密钥（32 字节）
     */
    private fun generateAesKey(): ByteArray {
        val key = ByteArray(32)
        SecureRandom().nextBytes(key)
        return key
    }

    @Nested
    @DisplayName("基础加密解密测试")
    inner class BasicEncryptDecryptTest {

        @Test
        @DisplayName("encrypt() 和 decrypt(): 应该正确加密和解密数据")
        fun testEncryptAndDecrypt() {
            // Given
            val key = generateAesKey()
            val plaintext = "Hello, World! 你好世界！".toByteArray(Charsets.UTF_8)

            // When
            val ciphertext: ByteArray = crypto.encrypt(plaintext, key)
            val decrypted: ByteArray = crypto.decrypt(ciphertext, key)

            // Then
            assertNotNull(ciphertext, "密文不应为空")
            assertTrue(ciphertext.isNotEmpty(), "密文不应为空数组")
            assertArrayEquals(plaintext, decrypted, "解密后的数据应该与原始数据相同")
        }

        @Test
        @DisplayName("encrypt(): 空数据应该正确处理")
        fun testEncryptEmptyData() {
            // Given
            val key = generateAesKey()
            val emptyData = ByteArray(0)

            // When
            val ciphertext: ByteArray = crypto.encrypt(emptyData, key)
            val decrypted: ByteArray = crypto.decrypt(ciphertext, key)

            // Then
            assertArrayEquals(emptyData, decrypted, "空数据加密解密应该正确")
        }

        @Test
        @DisplayName("encrypt(): 中文数据应该正确处理")
        fun testEncryptChineseText() {
            // Given
            val key = generateAesKey()
            val chineseText = "这是一段中文测试数据，包含特殊字符：！@#￥%……&*（）".toByteArray(Charsets.UTF_8)

            // When
            val ciphertext: ByteArray = crypto.encrypt(chineseText, key)
            val decrypted: ByteArray = crypto.decrypt(ciphertext, key)

            // Then
            assertArrayEquals(chineseText, decrypted, "中文数据加密解密应该正确")
        }

        @Test
        @DisplayName("encrypt(): 二进制数据应该正确处理")
        fun testEncryptBinaryData() {
            // Given
            val key = generateAesKey()
            val binaryData = byteArrayOf(0x00, 0x01, 0x02, 0xFF.toByte(), 0xFE.toByte(), 0xFD.toByte())

            // When
            val ciphertext: ByteArray = crypto.encrypt(binaryData, key)
            val decrypted: ByteArray = crypto.decrypt(ciphertext, key)

            // Then
            assertArrayEquals(binaryData, decrypted, "二进制数据加密解密应该正确")
        }

        @Test
        @DisplayName("encrypt(): 大数据块应该正确处理")
        fun testEncryptLargeData() {
            // Given
            val key = generateAesKey()
            val largeData = "A".repeat(10000).toByteArray(Charsets.UTF_8)

            // When
            val ciphertext: ByteArray = crypto.encrypt(largeData, key)
            val decrypted: ByteArray = crypto.decrypt(ciphertext, key)

            // Then
            assertArrayEquals(largeData, decrypted, "大数据块加密解密应该正确")
        }
    }

    @Nested
    @DisplayName("crypto() 方法测试")
    inner class CryptoMethodTest {

        @Test
        @DisplayName("crypto(): 加密模式应该正确工作")
        fun testCryptoEncryptMode() {
            // Given
            val key = generateAesKey()
            val plaintext = "Test data for crypto method".toByteArray(Charsets.UTF_8)

            // When
            val ciphertext: ByteArray = crypto.crypto(plaintext, key, Cipher.ENCRYPT_MODE)
            val decrypted: ByteArray = crypto.crypto(ciphertext, key, Cipher.DECRYPT_MODE)

            // Then
            assertNotNull(ciphertext, "加密结果不应为空")
            assertTrue(ciphertext.isNotEmpty(), "加密结果不应为空数组")
            assertArrayEquals(plaintext, decrypted, "使用 crypto 方法加密解密应该正确")
        }

        @Test
        @DisplayName("crypto(): 解密模式应该正确工作")
        fun testCryptoDecryptMode() {
            // Given
            val key = generateAesKey()
            val plaintext = "Test data for decrypt mode".toByteArray(Charsets.UTF_8)
            val ciphertext = crypto.encrypt(plaintext, key)

            // When
            val decrypted: ByteArray = crypto.crypto(ciphertext, key, Cipher.DECRYPT_MODE)

            // Then
            assertArrayEquals(plaintext, decrypted, "使用 crypto 方法解密应该正确")
        }

        @Test
        @DisplayName("crypto(): 无效模式应该抛出异常")
        fun testCryptoInvalidMode() {
            // Given
            val key = generateAesKey()
            val data = "test".toByteArray()
            val invalidMode = 999

            // When & Then
            assertThrows<Exception> {
                crypto.crypto(data, key, invalidMode)
            }
        }
    }

    @Nested
    @DisplayName("密钥验证测试")
    inner class KeyValidationTest {

        @Test
        @DisplayName("encrypt(): 错误长度的密钥应该抛出异常")
        fun testEncryptWithInvalidKeyLength() {
            // Given
            val invalidKey = ByteArray(16) // AES 需要 32 字节密钥
            val data = "test".toByteArray()

            // When & Then
            assertThrows<IllegalArgumentException> {
                crypto.encrypt(data, invalidKey)
            }
        }

        @Test
        @DisplayName("decrypt(): 错误长度的密钥应该抛出异常")
        fun testDecryptWithInvalidKeyLength() {
            // Given
            val validKey = generateAesKey()
            val invalidKey = ByteArray(16)
            val data = "test".toByteArray()
            val ciphertext = crypto.encrypt(data, validKey)

            // When & Then
            assertThrows<IllegalArgumentException> {
                crypto.decrypt(ciphertext, invalidKey)
            }
        }

        @Test
        @DisplayName("crypto(): 错误长度的密钥应该抛出异常")
        fun testCryptoWithInvalidKeyLength() {
            // Given
            val invalidKey = ByteArray(16)
            val data = "test".toByteArray()

            // When & Then
            assertThrows<IllegalArgumentException> {
                crypto.crypto(data, invalidKey, Cipher.ENCRYPT_MODE)
            }
        }
    }

    @Nested
    @DisplayName("跨密钥测试")
    inner class CrossKeyTest {

        @Test
        @DisplayName("decrypt(): 不同密钥应该无法解密")
        fun testDecryptWithDifferentKey() {
            // Given
            val key1 = generateAesKey()
            val key2 = generateAesKey()
            val plaintext = "Test data".toByteArray(Charsets.UTF_8)
            val ciphertext = crypto.encrypt(plaintext, key1)

            // When & Then
            assertThrows<Exception> {
                crypto.decrypt(ciphertext, key2)
            }
        }
    }

    @Nested
    @DisplayName("一致性测试")
    inner class ConsistencyTest {

        @Test
        @DisplayName("encrypt(): 相同输入应该产生不同密文（如果使用随机IV）")
        fun testEncryptConsistency() {
            // Given
            val key = generateAesKey()
            val plaintext = "Consistency test data".toByteArray(Charsets.UTF_8)

            // When
            val ciphertext1 = crypto.encrypt(plaintext, key)
            val ciphertext2 = crypto.encrypt(plaintext, key)

            // Then
            // 注意：如果加密算法使用固定模式（如ECB），密文可能相同
            // 这里我们主要验证解密的一致性
            val decrypted1 = crypto.decrypt(ciphertext1, key)
            val decrypted2 = crypto.decrypt(ciphertext2, key)

            assertArrayEquals(plaintext, decrypted1, "第一次解密应该正确")
            assertArrayEquals(plaintext, decrypted2, "第二次解密应该正确")
        }

        @Test
        @DisplayName("encrypt() 和 decrypt(): 多次操作应该保持一致性")
        fun testMultipleOperationsConsistency() {
            // Given
            val key = generateAesKey()
            val testData = "多次操作一致性测试".toByteArray(Charsets.UTF_8)

            // When & Then
            repeat(10) {
                val encrypted = crypto.encrypt(testData, key)
                val decrypted = crypto.decrypt(encrypted, key)
                assertArrayEquals(testData, decrypted, "第 ${it + 1} 次加密解密应该一致")
            }
        }
    }
}
